﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Quantization Techniques for Visualization of High Dynamic Range Pictures
    /// C. Schlick
    /// </summary>
    public sealed class Schlick : Operator
    {
        public const string NAME = "schlick";

        private const int MIN_VAL = 0; // minimum output intensity
        private const int MAX_VAL = 255; // maximum output intensity

        public bool Uniform
        {

            get
            {
                return this.uniform;
            }
            set
            {
                this.uniform = value;
            }
        }

        public int MinValue
        {

            get
            {
                return this.minValue;
            }
            set
            {
                this.minValue = Math.Min(Math.Max(value, MIN_VAL), this.maxValue);
            }
        }

        public int MaxValue
        {

            get
            {
                return this.maxValue;
            }
            set
            {
                this.maxValue = Math.Min(Math.Max(value, this.minValue), MAX_VAL);
            }
        }

        public double Weight
        {

            get
            {
                return this.weight;
            }
            set
            {
                this.weight = Math.Min(Math.Max(value, 0), 1);
            }
        }

        private bool uniform = false;
        private int minValue = 10;
        private int maxValue = 255;
        private double weight = 0.5;

        public Schlick(bool uniform, int minValue, int maxValue, double weight)
        {

            this.uniform = uniform;
            this.minValue = minValue;
            this.maxValue = maxValue;
            this.Weight = weight;
        }

        public Schlick()
        {
        }

        protected override void Run(float[][,] mapInput, int width, int height, ref float[][,] mapOutput)
        {

            int x = 0, y = 0;

            // get loVal and hiVal
            float val = 0;
            float loVal = Single.MaxValue;
            float hiVal = Single.MinValue;

            for (y = 0; y < height; y++)
                for (x = 0; x < width; x++)
                {

                    val = mapInput[0][x, y];

                    if (val < loVal && val != 0)
                        loVal = val;

                    if (val > hiVal)
                        hiVal = val;
                }

            // get the p value
            double p = ((this.minValue * hiVal) / (this.maxValue * loVal));

            // tone map
            double u = 0, v = 0;
            float val_ = 0;

            if (this.uniform)
            {

                #region uniform tone mapping

                for (y = 0; y < height; y++)
                    for (x = 0; x < width; x++)
                    {

                        u = ((double)mapInput[1][x, y] / mapInput[2][x, y]);
                        v = ((double)mapInput[0][x, y] / mapInput[2][x, y]);

                        val = (float)(mapInput[2][x, y] * (0.299 + 0.587 * u + 0.114 * v));
                        val_ = (float)((p * val) / (p * val - val + hiVal));

                        mapOutput[2][x, y] = (float)Math.Min(Math.Max(val_ / (0.299 + 0.587 * u + 0.114 * v), 0), 1);
                        mapOutput[1][x, y] = (float)Math.Min(Math.Max(u * mapOutput[2][x, y], 0), 1);
                        mapOutput[0][x, y] = (float)Math.Min(Math.Max(v * mapOutput[2][x, y], 0), 1);
                    }

                #endregion
            }
            else
            {

                #region non-uniform tone mapping

                double miVal = Math.Sqrt(loVal * hiVal);
                double p_ = 0;

                for (y = 0; y < height; y++)
                    for (x = 0; x < width; x++)
                    {

                        u = ((double)mapInput[1][x, y] / mapInput[2][x, y]);
                        v = ((double)mapInput[0][x, y] / mapInput[2][x, y]);

                        val = (float)(mapInput[2][x, y] * (0.299 + 0.587 * u + 0.114 * v));
                        p_ = (p * (1 - this.weight + this.weight * (val / miVal)));
                        val_ = (float)((p_ * val) / (p_ * val - val + hiVal));

                        mapOutput[2][x, y] = (float)Math.Min(Math.Max(val_ / (0.299 + 0.587 * u + 0.114 * v), 0), 1);
                        mapOutput[1][x, y] = (float)Math.Min(Math.Max(u * mapOutput[2][x, y], 0), 1);
                        mapOutput[0][x, y] = (float)Math.Min(Math.Max(v * mapOutput[2][x, y], 0), 1);
                    }

                #endregion
            }
        }

        protected override void SetParam(string param, string value)
        {

            switch (param)
            {
                case "uniform":
                    this.Uniform = Boolean.Parse(value);
                    break;
                case "minvalue":
                    this.MinValue = Int32.Parse(value);
                    break;
                case "maxvalue":
                    this.MaxValue = Int32.Parse(value);
                    break;
                case "weight":
                    this.weight = Double.Parse(value);
                    break;
            }
        }

        private void Tonemap(float[][,] map)
        {

            int width = map[0].GetLength(0);
            int height = map[0].GetLength(1);
            int x = 0, y = 0;

            // get loVal and hiVal
            float val = 0;
            float loVal = Single.MaxValue;
            float hiVal = Single.MinValue;

            for (y = 0; y < height; y++)
                for (x = 0; x < width; x++)
                {

                    val = map[0][x, y];

                    if (val < loVal && val != 0)
                        loVal = val;

                    if (val > hiVal)
                        hiVal = val;
                }

            // get the p value
            double p = ((this.minValue * hiVal) / (this.maxValue * loVal));

            // tone map
            if (this.uniform)
            {

                #region uniform tone mapping

                for (y = 0; y < height; y++)
                    for (x = 0; x < width; x++)
                    {

                        val = map[1][x, y];

                        map[1][x, y] = (float)((p * val) / (p * val - val + hiVal));
                    }

                #endregion
            }
            else
            {

                #region non-uniform tone mapping

                double miVal = Math.Sqrt(loVal * hiVal);
                double p_ = 0;

                for (y = 0; y < height; y++)
                    for (x = 0; x < width; x++)
                    {

                        val = map[1][x, y];

                        p_ = (p * (1 - this.weight + this.weight * (val / miVal)));

                        map[1][x, y] = (float)((p_ * val) / (p_ * val - val + hiVal));
                    }

                #endregion
            }
        }
    }
}
